package Server;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import java.util.Set;
import java.util.Map.Entry;

import common.ClientInterface;
import common.FileLogger;
import common.RmiInterface;
import common.exception.InvalidTokenException;


/**
 * La classe si occupa di fornire il supporto per rmi.
 * Implementa RmiInterface e ClientInterface, con cui fonisce rispettivamente: al client i metodi ping(), add(), e dell(); al server notifyTurn(); 
 * 
 * @author Jacopo Ferrigno
 *
 */
public class RmiGame extends UnicastRemoteObject  implements  Serializable, RmiInterface, ClientInterface, Runnable {

	private static final long serialVersionUID = 1L;
	//Costanti per i nomi degli oggetti remoti
	private String ServerName="RmiServer";
	private String Game="Game";
	//private String ClientName="RmiClient";
	//Map in cui memorizzare la lista dei giocatori
	private Map<String, ClientInterface> playerList;
	private Map<String, Integer> playerToken;
	private Map<String, RmiInterface> playerPing;
	//Gestione chiusura
	boolean close=false;
	//ogni quanto viene controllato che il client sia attivo, in secondi
	public final static int SLEEP_TIME=10;

	transient private FileLogger logger;
	/**
	 * Costruttore unico: crea un'istanza di RmiGame, bindando 2 oggetti (Game e se stesso )al rmiRegistry.
	 * Questi sono poi messi a disposizione dei client che eventualmente si connettano.
	 * 
	 * 
	 */
	public RmiGame() throws RemoteException {

		this.playerList=new HashMap<String, ClientInterface>();
		this.playerToken=new HashMap<String, Integer>();
		this.playerPing=new HashMap<String, RmiInterface>();
		this.logger = new FileLogger();		

		//Start the rmiregistry and all stuff
		try{
			//lookup for a rmiregistry o create it

			LocateRegistry.getRegistry("localhost",1099);
			//Provo a bindarmi sul server
			try{
				Naming.rebind(this.Game, Server.Game.getGame());
				Naming.rebind(this.ServerName, this);
			}
			catch(RemoteException e){				
				LocateRegistry.createRegistry(1099);
				Naming.rebind(this.Game, Server.Game.getGame());
				Naming.rebind(this.ServerName, this);
			}
		}
		catch(RemoteException e){	
			this.logger.logError("RemoteException thrown");
		} catch(MalformedURLException e){	
			this.logger.logError("MalformedURLException thrown");
		}
		Thread thread=new Thread(this);
		thread.setName("RmiGame");
		thread.start();
	}

	/**
	 * Notifica ai client connessi che il turno e' cambiato ed e' attualmente il turno dell'utente passato a parametro
	 * 
	 * @param newUser utente di cui e' il turno 
	 * 
	 * @throws RemoteException nel caso sorga qualche problema nella comunicazione con i client
	 */
	@Override
	public void notifyTurn(String newUser) throws RemoteException {
		System.out.println("Turno di: "+newUser);
		for(Map.Entry<String, ClientInterface> player: this.playerList.entrySet() ){
			player.getValue().notifyTurn(newUser);
		}

	}
	
	/*
	 * (non-Javadoc)
	 * @see common.RmiInterface#add(java.lang.String, common.ClientInterface, common.RmiInterface, int)
	 */
	@Override
	public synchronized int add(String host, ClientInterface newClient, RmiInterface newPingClient, int token)
	throws RemoteException {

		if(newClient!=null){
			this.playerList.put(host, newClient);
			this.playerToken.put(host, token);
			this.playerPing.put(host, newPingClient);
			return 0;
		}
		return 1;
	}


	@Override
	public synchronized int dell(String host) {
		if(!this.playerList.containsKey(host))
			return -1;
		this.playerList.remove(host);	
		this.playerToken.remove(host);
		this.playerPing.remove(host);

		return 0;
	}


	@Override
	public synchronized int ping(int msg) throws RemoteException {
		return msg;
	}

	public void run(){
		while(!toClose()){
			Iterator <Entry<String , RmiInterface>> iterator = this.playerPing.entrySet().iterator();
			while (iterator.hasNext()){
				Entry<String , RmiInterface> player= iterator.next();
				try{player.getValue().ping(1); }
				catch (RemoteException e) {}
				finally{
					iterator.remove();
					this.playerList.remove(player.getKey());

					this.playerToken.remove(player.getKey());

				}
			}
			/*for(Map.Entry<String, RmiInterface> player: this.playerPing.entrySet() ){
				try{player.getValue().ping(1); }
				catch (RemoteException e) {
					e.printStackTrace();
					try {
						this.emegencyLogout(player.getKey());
					} catch (RemoteException e1) {
						throw new RuntimeException();
					} catch (InvalidTokenException e1) {
						throw new RuntimeException();
					}finally{
						this.playerList.remove(player.getKey());
						this.playerPing.remove(player.getKey());
						this.playerToken.remove(player.getKey());
					}
				}
				
			}*/
			try {Thread.sleep(SLEEP_TIME*1000);} 
			catch (InterruptedException e) {e.printStackTrace();}
		}
		closeAll();
	}


	private void emegencyLogout(String playerName) throws RemoteException, InvalidTokenException{
		Integer token=null;		
		if((token=this.playerToken.get(playerName))!=null){
			(Server.Game.getGame()).logout(token.intValue());
			this.logger.logError("Utente \""+playerName+"\" disconnesso inaspettatamente");
		}
	}
	
	/**
	 * 
	 * Avvia il processo di chiusura di RmiGame
	 * 
	 */

	public void close(){
		this.close=true;
	}

	private boolean toClose(){
		return this.close;
	}

	private void closeAll(){

	}

}
